// 防抖
// 防抖：控制事件触发的次数；节流：控制事件触发的频率
// 防抖函数原理：在事件被触发n秒后再执行回调，如果在这n秒内又被触发，则重新计时。
// 按钮提交场景：防止多次提交按钮，只执行最后提交的一次
const deBounce = function(fn,delay=2000){
    let time = null;
    return function (...args){
        if (time) {
            clearTimeout(time)
        }
        time = setTimeout(() => {
            fn.call(this,...args)
        }, delay);
    }
}


// 节流函数原理:规定在一个单位时间内，只能触发一次函数。如果这个单位时间内触发多次函数，只有一次生效。
// 单位时间内只执行一次
// 拖拽场景：固定时间内只执行一次，防止超高频次触发位置变动
// 定时器
// 会在定时器结束的时候执行
const throttle = (fn, delay = 2000) => {
   let flag = true;
   return (...args) => {
     if (!flag) return;
     flag = false;
     setTimeout(() => {
       fn.apply(this, args);
       flag = true;
     }, delay);
   };
}
// 时间戳
// 会立刻执行
const throttle1 = (fn, delay = 2000) => {
    let  pre = 0;
    return (...args) => {
        let now = new Date();
        if (now - pre > delay)  {
            fn(...args);
            pre  =  now;
        }
    };
}
// 深克隆（deepclone）
// 简单版 这种方法有局限性 需要保证对象是json安全的
// const copySimpObj = JSON.parse(JSON.stringify(oldObj));
let objaaa = {
    a : 'zhangsan',
    b : 'lisi',
    c : {
        d:3,
        e:4
    },
    f : [1,2,3,4]
}
// console.log(deepCopy(objaaa));
function deepCopy(obj){
    let result
    // 判断是不是简单数据类型
    if (obj && typeof obj == 'object') {
        result = obj.constructor == Array ? [] : {};
        for (const key in obj) {
            result[key] = (typeof obj[key]) == 'object' ? deepCopy(obj[key]) : obj[key]
        }
    }else{
        result = obj
    }
    return result
}
// 实现event bus
class EventEmeitter {
    constructor() {
      this._events = this._events || new Map(); // 储存事件/回调键值对
      this._maxListeners = this._maxListeners || 10; // 设立监听上限
    }
  }
  // 触发名为type的事件
  EventEmeitter.prototype.emit = function(type, ...args) {
    let handler;
    // 从储存事件键值对的this._events中获取对应事件回调函数
    handler = this._events.get(type);
    if (args.length > 0) {
      handler.apply(this, args);
    } else {
      handler.call(this);
    }
    return true;
  };
  // 监听名为type的事件
  EventEmeitter.prototype.addListener = function(type, fn) {
    // 将type事件以及对应的fn函数放入this._events中储存
    if (!this._events.get(type)) {
      this._events.set(type, fn);
    }
  };
// instanceof 检查后面构造函数的的显式原型对象是不是在前面实例的原型链上
// 模拟 instanceof
function instance_of(left,right){
    let proto = left.__proto__; //取左边的隐式原型
    let prototype = right.prototype; //取右边的显示原型
    while (true) {
        if(proto == null) return false;
        if (proto === prototype) return true // 如果右边的显示原型对象 在左边的原型链上则true
        proto = proto.__proto__;
    }
}
function Student(){
    this.name = 'zhangsan'
}
Student.prototype.age = 18
// let student = new Student()
// console.log(student.__proto__);
// console.log(instance_of(student,Object));
// constructor是构造函数对应的原型对象具有的属性 是Student.prototype.constructor 所以实例可以直接访问constructor
// a.constructor = a.__proto__.constructor = A.prototype.constructor =A


// new操作符做了这些事：
// 它创建了一个全新的对象
// 它会被执行[[Prototype]]（也就是__proto__）链接
// 它使this指向新创建的对象
// 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
// 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error)，那么new表达式中的函数调用将返回该对象引用
// objectFactory(name, 'cxk', '18')
// function objectFactory() {
//   const obj = new Object();
//   const Constructor = [].shift.call(arguments);

//   obj.__proto__ = Constructor.prototype;

//   const ret = Constructor.apply(obj, arguments);

//   return typeof ret === "object" ? ret : obj;
// }
// 其实就三步， 
// 创建一个空对象
// 将对象实例的隐式原型 指向构造函数的原型
// 改变this指向 将构造函数中的this指向 创建的对象
function createNew(Con,...args){
    let obj = {};
    obj.__proto__  = Con.prototype;
    let result = Con.apply(obj,args)
    return result instanceof Object ? result : obj
}
let student1 = createNew(Student);
// console.log(student1.age);

// call做了什么:
// 将函数设为对象的属性
// 执行&删除这个函数
// 指定this到函数并传入给定参数执行函数
// 如果不传入参数，默认指向为 window
// 模拟 call bar.mycall(null);
//实现一个call方法：
// 将函数对象 作为传过来的对象的一个属性
// 拿到传过来的参数
// 执行这个函数 因为是这个对象执行的这个函数 所有函数体内的this就指向了这个对象
Function.prototype._call = function(context){
  if (context === null || context === undefined) {
     context = window;
  }
  context._fn = this;//这个this就是指的这个函数对象
  //拿到所有传过来的参数
  let args = [];
  //  这里要从下标1 开始 0的是传的obj对象
  for (let i = 1; i < arguments.length; i++) {
    args.push(arguments[i]);
  } 
  let result = context._fn(...args)
  delete context._fn;
  return result
}
let name = '张三'
function bar(a,b){
  console.log(this.name,a,b);
}
let barObj = {
  name : '李四'
}
// bar();
// bar.call(barObj)
// bar._call(barObj,1,2)
//实现一个apply方法：
// 和call差不多 只是传参形式变了下
Function.prototype.myApply = function(context,arr){
  if (context === null || context === undefined) {
    context = window
  }
  context._fn = this;
  let result = context._fn(...arr)
  delete context._fn
  return result
}
// bar.myApply(barObj,[1,2])
// Object.create()方法创建一个新对象，使用现有的对象来提供新创建的对象的__proto__。
function create(proto) {
  function F() {}
  F.prototype = proto;

  return new F();
}
let student2 = Object.create(Student.prototype);
// console.log(student2.age);
// es5继承的实现
function Person() {
  this.name = 'zhangsan';
  this.age = 18;
}
Person.prototype.sayHi = function() {
  console.log('---sayHi----');
}
function Man() {
  this.sex = '男'
  Person.call(this)
}
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;
let man = new Man();
// console.log(man.name,man.sex);
// man.sayHi()
// console.log(man.constructor,man.__proto__ === Man.prototype,Man.prototype.__proto__ === Person.prototype);
// es6继承
// class就相当于构造函数的语法糖
class Father{
  constructor(){
    this.name = 'zhangsan';
    this.age = 18;
  }
  say(){
    console.log('---say---');
  }
}
class Son extends Father{
  constructor(sex){
    super();
    this.sex = sex
  }
}
let son = new Son('女')
// console.log(son.name,son.sex);
// son.say();
// console.log(Object.getPrototypeOf(Son) === Father);//getPrototypeOf判断是否继承
// console.log(Son.prototype.constructor === Son);
// 实现JSON.parse
// var json = '{"name":"cxk", "age":25}';
// var obj = eval("(" + json + ")");
// console.log(obj);

/**
 * 手写一个promise
 * Promise/A+的规范
 * 三种状态 pending|fulfilled(resolved)|rejected
   当处于pending的时候可以转移到 fulfilled(resolved)或者rejected
   当处于 fulfilled(resolved)或者rejected的时候不可改变
 *
*/
// 先看一下promise的用法
const promise33 = function(bool){
  return new Promise(function(resolve,reject){
       resolve('6666');
   });
}
async  function promiseAsync(){
   let res = await promise33(true);
   return res;
}
promiseAsync().then(result => {
   console.log(result);
   //success
},err =>{
   // fail
})
function myPromise(constructor){
   let self = this;
   self.status = 'pending'//定义状态改变前的初始状态
   self.value = undefined;//定义状态为resolved的时候的状态
   self.reason = undefined;//定义状态为rejected的时候的状态
   function resolve(value){
       if(self.status === 'pending'){
           self.value = value;
           self.status = 'resolved'
       }
   }
   function reject(reason){
       if(self.status === 'pending'){
           self.reason = reason;
           self.status = 'rejected'
       }
   }
   // 捕获构造异常
   try {
       constructor(resolve,reject);
   } catch (e) {
       reject(e);
   }
}
myPromise.prototype.then = function(onFullfilled,onRejected){
   let self = this;
   switch (self.status) {
       case  "resolved":
       onFullfilled(self.value)
           break;
       case "rejected":
       onRejected(self.reason);
       break;
       default:
           break;
   }
}
var p = new myPromise(function(resolve,reject){
   resolve(1);
})
p.then(function(result){
   console.log(result);
})